#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
@File    : conftest.py
@Description : pytest配置文件，用于定义测试夹具和钩子函数
'''

import os
import json
import time
import pytest
import allure
from datetime import datetime
from typing import Dict, List, Any, Optional, Union

# 导入项目模块
from config.settings import config
from utils.log_handler import get_logger
from utils.http_client import HttpClient
from utils.excel_handler import ExcelHandler
from utils.json_handler import JsonHandler
from utils.dingtalk_handler import DingTalkHandler


# 创建日志记录器
logger = get_logger("pytest")


# 测试会话开始时的钩子函数
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config):
    """
    pytest配置钩子函数，在测试会话开始时调用
    
    Args:
        config: pytest配置对象
    """
    logger.info("开始测试会话")
    
    # 配置allure环境信息
    from config.settings import config as app_config
    allure_env_dir = os.path.join(app_config.allure_results_dir, 'environment.properties')
    os.makedirs(os.path.dirname(allure_env_dir), exist_ok=True)
    
    # 写入环境信息
    with open(allure_env_dir, 'w', encoding='utf-8') as f:
        f.write(f"Environment={app_config.environment}\n")
        f.write(f"BaseURL={app_config.base_url}\n")
        f.write(f"PythonVersion={pytest.__version__}\n")
        f.write(f"Timestamp={datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")


# 测试会话结束时的钩子函数
@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
    """
    pytest会话结束钩子函数，在测试会话结束时调用
    
    Args:
        session: pytest会话对象
        exitstatus: 退出状态码
    """
    logger.info(f"测试会话结束，退出状态码: {exitstatus}")


# 测试报告钩子函数
@pytest.hookimpl(hookwrapper=True)
def pytest_terminal_summary(terminalreporter, exitstatus, config):
    """
    pytest终端摘要钩子函数，在测试报告生成时调用
    
    Args:
        terminalreporter: 终端报告对象
        exitstatus: 退出状态码
        config: pytest配置对象
    """
    yield
    
    # 获取测试结果统计
    duration = time.time() - terminalreporter._sessionstarttime
    total = terminalreporter.stats.get('passed', []) + terminalreporter.stats.get('failed', []) + terminalreporter.stats.get('skipped', [])
    passed = len(terminalreporter.stats.get('passed', []))
    failed = len(terminalreporter.stats.get('failed', []))
    skipped = len(terminalreporter.stats.get('skipped', []))
    total_count = len(total)
    
    # 打印测试结果统计
    logger.info(f"测试结果统计:")
    logger.info(f"总用例数: {total_count}")
    logger.info(f"通过用例数: {passed}")
    logger.info(f"失败用例数: {failed}")
    logger.info(f"跳过用例数: {skipped}")
    logger.info(f"测试耗时: {round(duration, 2)}秒")
    
    # 发送钉钉通知
    try:
        dingtalk = DingTalkHandler()
        dingtalk.send_test_report(
            total=total_count,
            passed=passed,
            failed=failed,
            skipped=skipped,
            duration=duration,
            report_url=None  # 这里可以设置报告URL
        )
    except Exception as e:
        logger.error(f"发送钉钉通知失败: {str(e)}")


# HTTP客户端夹具
@pytest.fixture(scope="session")
def http_client():
    """
    HTTP客户端夹具，用于创建HTTP客户端实例
    
    Returns:
        HTTP客户端实例
    """
    client = HttpClient()
    logger.info(f"创建HTTP客户端，基础URL: {client.base_url}")
    yield client
    client.close()
    logger.info("关闭HTTP客户端")


# Excel处理器夹具
@pytest.fixture(scope="session")
def excel_handler():
    """
    Excel处理器夹具，用于创建Excel处理器实例
    
    Returns:
        Excel处理器实例
    """
    handler = ExcelHandler()
    logger.info(f"创建Excel处理器，文件路径: {handler.file_path}")
    return handler


# JSON处理器夹具
@pytest.fixture(scope="session")
def json_handler():
    """
    JSON处理器夹具，用于创建JSON处理器实例
    
    Returns:
        JSON处理器实例
    """
    handler = JsonHandler()
    logger.info(f"创建JSON处理器，文件路径: {handler.file_path}")
    return handler


# 测试数据夹具
@pytest.fixture(scope="function")
def test_data(request, excel_handler, json_handler):
    """
    测试数据夹具，用于获取测试数据
    
    Args:
        request: pytest请求对象
        excel_handler: Excel处理器实例
        json_handler: JSON处理器实例
        
    Returns:
        测试数据字典
    """
    # 获取测试用例ID
    case_id = request.node.name.split("[")[0]
    logger.info(f"获取测试数据，用例ID: {case_id}")
    
    # 尝试从Excel获取测试用例数据
    try:
        # 获取测试用例数据
        test_cases = excel_handler.get_test_cases()
        case_data = next((case for case in test_cases if case.get("用例ID") == case_id), None)
        
        # 获取测试数据
        test_data_list = excel_handler.get_test_data(case_id)
        
        # 将测试数据转换为字典
        data_dict = {}
        for data in test_data_list:
            data_dict[data.get("数据名称")] = data.get("数据值")
    except Exception as e:
        logger.warning(f"从Excel获取测试数据失败: {str(e)}，将尝试从JSON获取")
        # 如果Excel获取失败，尝试从JSON获取
        # 获取测试用例数据
        test_cases = json_handler.get_test_cases()
        case_data = next((case for case in test_cases if case.get("用例ID") == case_id), None)
        
        # 获取测试数据
        test_data_list = json_handler.get_test_data(case_id)
        
        # 将测试数据转换为字典
        data_dict = {}
        for data in test_data_list:
            data_dict[data.get("数据名称")] = data.get("数据值")
    
    # 合并测试用例数据和测试数据
    result = {}
    if case_data:
        result.update(case_data)
    result["test_data"] = data_dict
    
    return result


# 测试结果记录夹具
@pytest.fixture(scope="function")
def record_result(request, excel_handler, json_handler):
    """
    测试结果记录夹具，用于记录测试结果
    
    Args:
        request: pytest请求对象
        excel_handler: Excel处理器实例
        json_handler: JSON处理器实例
    """
    yield
    
    # 获取测试用例ID
    case_id = request.node.name.split("[")
    if len(case_id) > 0:
        case_id = case_id[0]
    else:
        case_id = request.node.name
    
    # 获取测试结果
    if request.node.rep_call.passed:
        status = "通过"
        result = "测试通过"
    elif request.node.rep_call.failed:
        status = "失败"
        result = f"测试失败: {request.node.rep_call.longrepr}"
    elif request.node.rep_call.skipped:
        status = "跳过"
        result = "测试跳过"
    else:
        status = "未知"
        result = "未知状态"
    
    # 记录测试结果
    logger.info(f"记录测试结果，用例ID: {case_id}, 状态: {status}")
    
    # 尝试写入Excel测试结果
    try:
        excel_handler.write_result(case_id, result, status)
    except Exception as e:
        logger.warning(f"写入Excel测试结果失败: {str(e)}，将尝试写入JSON测试结果")
        # 如果Excel写入失败，尝试写入JSON
        json_handler.write_result(case_id, result, status)


# 记录测试结果的钩子函数
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """
    pytest运行测试报告钩子函数，用于记录测试结果
    
    Args:
        item: 测试项
        call: 调用信息
    """
    outcome = yield
    rep = outcome.get_result()
    
    # 设置测试结果属性
    setattr(item, f"rep_{rep.when}", rep)
    
    # 记录测试结果到allure报告
    if rep.when == "call":
        if rep.passed:
            logger.info(f"测试通过: {item.nodeid}")
        elif rep.failed:
            logger.error(f"测试失败: {item.nodeid}")
            # 添加失败截图或日志到allure报告
            if hasattr(item, "funcargs") and "http_client" in item.funcargs:
                # 可以在这里添加失败时的额外信息，如请求日志等
                pass
        elif rep.skipped:
            logger.warning(f"测试跳过: {item.nodeid}")